#ifndef SPLINECURVE_H
#define SPLINECURVE_H


#include <QPointF>

#include <functional>
#include <QRectF>
#include <memory>
#include <map>
#include <unordered_set>
#include <QColor>


enum class TangentMode{
    Linear,
    Constant,
    Weighted,
    Auto,
    User,
    Break,
};

#define LIST_ALL_TANGENT_MODE \
    XX(Linear)                \
    XX(Constant)              \
    /*XX(Weighted)*/              \
    XX(Auto)                  \
    XX(User)                  \
    XX(Break)

class SplinePoint : public std::enable_shared_from_this<SplinePoint>{
public:
    using ptr = std::shared_ptr<SplinePoint>;

    enum Select{
        _None = 0,
        _Point,
        _Handle1,
        _Handle2
    };

    SplinePoint();
    SplinePoint(QPointF point, Select select = _None);

    qint64 getId() const {return m_id;};
    const QPointF& getPoint() const {return m_point;}
    const QPointF& getHandle1() const {return m_handle1;}
    const QPointF& getHandle2() const {return m_handle2;}
    QPointF& getPoint() {return m_point;}
    QPointF& getHandle1() {return m_handle1;}
    QPointF& getHandle2() {return m_handle2;}

    void setPosition(QPointF position);
    void movePoint(QPointF offset);
    void setTangentMode(TangentMode mode);
    TangentMode getTangentMode() const {return m_tangentMode;}
    bool hasHandle() const;
    Select check(const QRectF& rect);
    Select getSelect() const {return m_select;}
    bool isSelect() const {return m_select != _None;}
    void setSelect(Select select){m_select = select;};
    void autoHandle();

    void setPrev(ptr sp);
    ptr getPrev() const {return prev;}
    void setNext(ptr sp);
    ptr getNext() const {return next;}
private:
    qint64 m_id;
    TangentMode m_tangentMode = TangentMode::Linear;
    QPointF m_point;
    QPointF m_handle1;
    QPointF m_handle2;
    Select m_select = _None;

    ptr prev = nullptr;
    ptr next = nullptr;
};


class SplineCurve
{
public:
    using ptr = std::shared_ptr<SplineCurve>;
    SplineCurve(QString name="Curve");

    SplinePoint::ptr addPoint(QPointF point, SplinePoint::Select select = SplinePoint::Select::_None);
    void deletePoint();
    void setTangent(TangentMode tangent);

    void visitAllPoints(std::function<void(const SplinePoint::ptr&)> callback);
    void visitSelectedPoints(std::function<void(QPointF&)> callback);
    void setSelectPointsPosition(QPointF position);
    void selectPoints(const QRectF& rect, bool isAdded = true);
    void clearSelectPoints();
    void moveSelectPoints(const QPointF offset);
    std::vector<QPointF> getCurve(qreal left, qreal right);
    bool checkSelectPointTangentMode(TangentMode mode);

    int countSelectPoint() const {return m_selectPoints.size();}

    const QString& name();
    QColor color() const;
    void setColor(const QColor &newColor);
private:
    void movePoint(const SplinePoint::ptr& sp, const QPointF& offset);

private:
    QString m_name;
    QColor m_color;
    std::map<qreal, SplinePoint::ptr> m_points;
    std::unordered_set<SplinePoint::ptr> m_selectPoints;
};

#endif // SPLINECURVE_H
